# -*- coding: utf-8 -*-
# @Time    : 2022/4/26 19:33
# @Author  : wise-wjw
# @File    : TitanicDataAnalysis.py
# @Software: PyCharm

"""案例分析——泰坦尼克号生存预测分析"""

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

# 设置列对齐
pd.set_option('display.unicode.ambiguous_as_wide', True)
pd.set_option('display.unicode.east_asian_width', True)
# 强制显示所有属性值
pd.set_option('display.max_columns', None)

# TODO:1.读入数据，并查看数据的简单信息
# 导入数据
train = pd.read_csv("resources/train.csv")  # 导入训练数据集
test = pd.read_csv("resources/test.csv")  # 导入测试数据集

# 查看数据集的行数及维度，同时记录两数据集的行数，便于后续两数据集拼接后从中拆分出测试数据集用预测分析
train_row_num = train.shape[0]  # 训练集行数
test_row_num = test.shape[0]  # 测试集行数
print("train训练集的行数与维度：", train.shape)
print("test测试集的行数与维度：", test.shape)

# 合并两数据集
full = pd.concat([train, test], ignore_index=True)
print("合并后的full数据集行数与维度：", full.shape)

# TODO:2.数据清理和特征提取:查看缺失值并对缺失值进行处理;特征提取
# 查看数据信息
print("\n所有数据信息：\n", full.head())

# 查看每一列的数据类型和数据总数,并查看数据集缺失值的情况
print("\n各维度的数据类型和数据总数：")
full.info()
print("\n各维度缺失值详情：\n", full.isnull().sum())

# 查看数值类型维度的统计信息
print("\n数值维度列的统计信息：\n", full.describe())

## 处理缺失值
# 对Age维度的缺失值进行处理
print("\nAge的均值为：", full['Age'].mean().round(2))
full['Age'] = full['Age'].fillna(full['Age'].mean())  # 用均值填补
full['Age'].info()  # 查看是否填补完成

# 对Fare维度的缺失值进行处理
print("\nFare的均值为：", full['Fare'].mean().round(2))
full['Fare'] = full['Fare'].fillna(full['Fare'].mean())  # 用均值填补
full['Fare'].info()  # 查看是否填补完成

# 对Embarked维度的缺失值进行处理
print("\n---Embarked维度的数据种类：", list(pd.unique(full['Embarked'])))
print("---Embarked维度各种类数据出现的次数：\n", full['Embarked'].value_counts())
print("---Embarked维度最常见的数据为：", full['Embarked'].mode()[0])
full['Embarked'] = full['Embarked'].fillna(full['Embarked'].mode()[0])  # 用最常见的数据（可看作众数）填补
full['Embarked'].info()  # 查看是否填补完成

# 对Cabin维度的缺失值进行处理
print("\n对Cabin维度的缺失值进行处理:")
full['Cabin'] = full['Cabin'].fillna('U')  # 用U（表示未知）来填充
full['Cabin'].info()  # 查看是否填补完成

## 对部分维度数据进行编码，将其转换为数值类型，便于后续预测分析
# 对Sex（性别）进行01编码
sex_dict = {'male': 1, 'female': 0}  # 编码规则，01编码
full['Sex'] = full['Sex'].map(sex_dict)  # 进行编码转换
print("\n查看Sex是否编码成功：\n", full.head(5))  # 查看Sex是否编码成功

# 对Embarked（登船港）进行one_hot编码（独热编码）
embarkedDf = pd.get_dummies(full['Embarked'], prefix='Embarked')
full = pd.concat([full, embarkedDf], axis=1)  # 将编码好的数据以列拼接的方式与原full连接
full.drop(['Embarked'], axis=1, inplace=True)  # 删除full数据集中的Embarked维度，此维度已进行编码需丢弃
print("\n查看Embarked是否独热编码成功：\n", full.head(5))  # 查看Embarked是否独热编码成功

# Pclass（客舱等级）进行one_hot编码（独热编码）
pclassDf = pd.get_dummies(full['Pclass'], prefix='Pclass')
full = pd.concat([full, pclassDf], axis=1)
full.drop(['Pclass'], axis=1, inplace=True)
print("\n查看Pclass是否独热编码成功：\n", full.head(5))

# Cabin（客舱号）进行one_hot编码（独热编码）
full['Cabin'] = full['Cabin'].map(lambda c: c[0])  # 先进行客舱号转换，只留下首字母
cabinDf = pd.get_dummies(full['Cabin'], prefix='Cabin')  # 再进行独热编码
full = pd.concat([full, cabinDf], axis=1)
full.drop(['Cabin'], axis=1, inplace=True)
print("\n查看Cabin是否独热编码成功：\n", full.head(5))

## 数据维度融合和删除处理
# 对Parch（同乘的父母/小孩数）和SibSp（同乘的兄弟姐妹/配偶数）两维度数据（类型为数值型）进行融合编码处理，将融合后的维度称为family
full['Family'] = full['SibSp'] + full['Parch'] + 1  # 加1，表示将自己也算入家庭成员数量
full.drop(['SibSp', 'Parch'], axis=1, inplace=True)
print("\n查看family维度是否融合成功：\n", full.head(5))

# 删除Name（乘客姓名）、Ticket（船票编号）
full.drop(['Name', 'Ticket'], axis=1, inplace=True)
print("\n删除Name（乘客姓名）、Ticket（船票编号）后,再次查看数据集各维度信息:")
full.info()  # 再次查看数据集各维度信息

# TODO:3.特征选择:不同变量跟生存情况的关系分析;特征评估;特征筛选;
# 分析各维度数据对Survived（存活情况）的影响
# 生成各维度间的相关性矩阵
corrDf = full.corr()
# sort_values方法可以输出各个维度与生成情况（Survived）的相关系数，反映了各个维度对Survived（存活情况）的影响大小
print("\n各个维度与生成情况（Survived）的相关系数:\n", corrDf['Survived'].sort_values(ascending=False))  # 输出结果按降序排列
# 用seaborn库的heatmap方法画出维度间的相关性热度图
plt.figure(figsize=(15, 12))
sns.heatmap(corrDf, annot=True)
plt.show()

## 对test数据进行生存预测
# 提取对Survived（存活情况）的影响较大的维度作为特征集:Sex/Pclass_X/Fare/Cabin_X
new_full = pd.concat([full['Sex'],  # 性别
                      full['Fare'],  # 船票价格
                      full['Pclass_1'],  # 客舱等级1
                      full['Pclass_2'],  # 客舱等级2
                      full['Pclass_3'],  # 客舱等级3
                      full['Cabin_A'],  # 客舱号A
                      full['Cabin_B'],  # 客舱号B
                      full['Cabin_C'],  # 客舱号C
                      full['Cabin_D'],  # 客舱号D
                      full['Cabin_E'],  # 客舱号E
                      full['Cabin_F'],  # 客舱号F
                      full['Cabin_G'],  # 客舱号G
                      full['Cabin_T'],  # 客舱号T
                      full['Cabin_U'],  # 客舱号U
                      ], axis=1)
print("\n经筛选后的特征集:\n", new_full.head(5))

# TODO:4.建立模型:特征工程;整理训练数据
# 整体处理训练集和测试集
x_train = new_full.iloc[0:train_row_num, :]  # 训练集:特征
y_train = full.loc[0:train_row_num - 1, 'Survived']  # 训练集:标签
x_test = new_full.iloc[train_row_num:, :]  # 测试集:特征

# 建立逻辑回归模型
model = LogisticRegression(solver='liblinear', class_weight="balanced")

# TODO:5.模型训练和预测:模型训练;整理预测数据;结果评估
# 训练模型
model.fit(x_train.iloc[0:-500, :], y_train.iloc[0:-500])
# 评估模型
print("\n模型的正确预测率:\n", accuracy_score(model.predict(x_train.iloc[-500:, :]), y_train.iloc[-500:].values.reshape(-1, 1)))
# 对预测数据集中的生存情况进行预测
prediction = model.predict(x_test)
# 得到测试集的乘客ID
passenger_id = full.loc[train_row_num:, 'PassengerId']
# 数据框：乘客id，预测生存情况的值
predictionDf = pd.DataFrame(
    {'PassengerId': passenger_id,
     'Survived': prediction}
)
print("\n预测结果的行数及维度:\n", predictionDf.shape)
print("\n预测测试集的乘客的存活情况:\n", predictionDf.head(10))
# 保存预测结果到新的csv文件中
predictionDf.to_csv('resources/Titanic_prediction.csv', index=False)
